#include "quickjs-libc.h"
#include "quickjs.h"
#include <cmath>
#include <cstring>
#include <iostream>
#include <string>

JSClassID newClassId = 0;

struct EventState {
  std::string type;
};

static JSValue js_print(JSContext *ctx, JSValueConst thiz, int argc,
                        JSValueConst *argv) {
  auto str = JS_ToCString(ctx, argv[0]);
  std::cout << "msg: " << str << std::endl;
  JS_FreeCString(ctx, str);
  return JS_UNDEFINED;
}

static JSValue Event_constructor(JSContext *ctx, JSValueConst new_target,
                                 int argc, JSValueConst *argv) {
  JSValue prototype;
  JSValue result = JS_UNDEFINED;
  auto id = newClassId;

  prototype = JS_GetPropertyStr(ctx, new_target, "prototype");
  if (JS_IsException(result)) {
    return JS_EXCEPTION;
  }
  if (argc == 0 || !JS_IsString(argv[0])) {
    return JS_ThrowTypeError(ctx, "%s", "Invalid event type");
  }
  result = JS_NewObjectProtoClass(ctx, prototype, id);
  JS_FreeValue(ctx, prototype);

  auto eventState = (EventState *)js_mallocz(ctx, sizeof(EventState));
  auto t = JS_ToCString(ctx, argv[0]);
  eventState->type = t;
  JS_FreeCString(ctx, t);
  JS_SetOpaque(result, eventState);
  return result;
}

static JSValue set_readonly(JSContext *ctx, JSValueConst thiz, JSValue val,
                            int magic) {
  return JS_UNDEFINED;
}

static JSValue get_type_Event(JSContext *ctx, JSValueConst thiz, int magic) {
  auto eventState = (EventState *)JS_GetOpaque(thiz, newClassId);
  return JS_NewString(ctx, &eventState->type[0]);
}

JSCFunctionListEntry Event_instanceMethods[1] = {
    JS_CGETSET_MAGIC_DEF("type", get_type_Event, set_readonly, 0),
};

void registerEventClass(JSContext *ctx) {
  JSClassID id = 0;
  newClassId = JS_NewClassID(&id);
  auto rt = JS_GetRuntime(ctx);
  char *className = "Event";
  JSClassDef classDef;
  classDef.class_name = className;
  classDef.finalizer = [](JSRuntime *rt, JSValue val) {
    auto s = (EventState *)JS_GetOpaque(val, newClassId);
    if (s != NULL) {
      js_free_rt(rt, s);
    }
  };
  classDef.gc_mark = NULL;
  classDef.exotic = NULL;
  classDef.call = NULL;

  JS_NewClass(rt, newClassId, &classDef);

  JSValue prototype = JS_NewObject(ctx);
  // add instance method
  JS_SetPropertyFunctionList(ctx, prototype, Event_instanceMethods, 1);
  auto new_class = JS_NewCFunction2(ctx, Event_constructor, className, 1,
                                    JS_CFUNC_constructor, 0);
  auto global_obj = JS_GetGlobalObject(ctx);
  JS_DefinePropertyValueStr(ctx, global_obj, className,
                            JS_DupValue(ctx, new_class),
                            JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
  JS_SetConstructor(ctx, new_class, prototype);
  JS_SetClassProto(ctx, newClassId, prototype);
  JS_FreeValue(ctx, new_class);
  JS_FreeValue(ctx, global_obj);
}

int main() {
  JSRuntime *rt;
  JSContext *ctx;
  JSValue evalVal;
  rt = JS_NewRuntime();
  ctx = JS_NewContext(rt);

  registerEventClass(ctx);

  auto global_object = JS_GetGlobalObject(ctx);
  auto printF = JS_NewCFunction(ctx, js_print, "print", 1);
  JS_SetPropertyStr(ctx, global_object, "print", printF);

  evalVal = JS_Eval(ctx, "let e = new Event('test1');\nprint(e.type);", 42,
                    "<input>", 0);

  JS_FreeValue(ctx, global_object);
  JS_FreeValue(ctx, evalVal);

  JS_FreeContext(ctx);
  JS_FreeRuntime(rt);
  return 0;
}

// Ok so you can't use std::String for storing the string generated by Quickjs it seems to create issues like this. As std::string are managed by C++ and the memory where this strings are created is in Heap of QuickJS runtime.
/**
struct EventState{
    char* type;
};
auto t = JS_ToCString(ctx, argv[0]);
eventState->type = strdup(t); //we will still need to free this
JS_FreeCString(ctx, t);
classDef.finalizer=[](JSRuntime* rt, JSValue val){
auto s  = (EventState*)JS_GetOpaque(val, newClassId);
if(s!=NULL){
    free(s->type);
    js_free_rt(rt, s);
 }
};
*/